/*
 * Copyright 2014 mango.jfaster.org
 *
 * The Mango Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package org.jfaster.mango.jdbc;

import org.jfaster.mango.exception.jdbc.MetaDataAccessException;
import org.jfaster.mango.util.PatternMatchUtils;
import org.jfaster.mango.util.logging.InternalLogger;
import org.jfaster.mango.util.logging.InternalLoggerFactory;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @author ash
 */
public class SQLErrorCodesFactory {

    protected final static InternalLogger logger = InternalLoggerFactory.getInstance(SQLErrorCodesFactory.class);

    private final static SQLErrorCodesFactory instance = new SQLErrorCodesFactory();

    private final Map<DataSource, SQLErrorCodes> dataSourceCache = new HashMap<DataSource, SQLErrorCodes>(16);

    private final Map<String, SQLErrorCodes> errorCodesMap;

    public SQLErrorCodesFactory() {
        this.errorCodesMap = buildErrorCodesMap();
    }

    public static SQLErrorCodesFactory getInstance() {
        return instance;
    }

    public SQLErrorCodes getErrorCodes(DataSource dataSource) {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking up default SQLErrorCodes for DataSource [" + dataSource + "]");
        }

        synchronized (this.dataSourceCache) {
            SQLErrorCodes sec = this.dataSourceCache.get(dataSource);
            if (sec != null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("SQLErrorCodes found in cache for DataSource [" +
                            dataSource.getClass().getName() + '@' + Integer.toHexString(dataSource.hashCode()) + "]");
                }
                return sec;
            }
            try {
                String dbName = JdbcUtils.fetchDatabaseProductName(dataSource);
                if (dbName != null) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Database product name cached for DataSource [" +
                                dataSource.getClass().getName() + '@' + Integer.toHexString(dataSource.hashCode()) +
                                "]: name is '" + dbName + "'");
                    }
                    sec = getErrorCodes(dbName);
                    this.dataSourceCache.put(dataSource, sec);
                    return sec;
                }
            } catch (MetaDataAccessException ex) {
                logger.warn("Error while extracting database product name - falling back to empty error codes", ex);
            }
        }

        // 失败返回空的SQLErrorCodes
        return SQLErrorCodes.EMPTY;
    }

    private SQLErrorCodes getErrorCodes(String dbName) {
        SQLErrorCodes sec = this.errorCodesMap.get(dbName);
        if (sec == null) {
            for (SQLErrorCodes candidate : this.errorCodesMap.values()) {
                if (PatternMatchUtils.simpleMatch(candidate.getDatabaseProductNames(), dbName)) {
                    sec = candidate;
                    break;
                }
            }
        }

        if (sec != null) {
            return sec;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("SQL error codes for '" + dbName + "' not found");
        }
        return SQLErrorCodes.EMPTY;
    }

    private Map<String, SQLErrorCodes> buildErrorCodesMap() {
        Map<String, SQLErrorCodes> errorCodesMap = new HashMap<String, SQLErrorCodes>();
        for (SQLErrorCodes errorCodes : SQLErrorCodes.values()) {
            if (errorCodes != SQLErrorCodes.EMPTY) {
                errorCodes.init();
                errorCodesMap.put(errorCodes.name(), errorCodes);
            }
        }
        return errorCodesMap;
    }

}
